<?php

namespace tbollmeier\kakuromaster\core;

class Puzzle
{
    const HORIZONTAL = 1;
    const VERTICAL = 2;
    
    /**
     * Create a kakuro puzzle from JSON data.
     * The JSON format is to be expected as follows:
     {
        "groups": [
            {
            "row": <row>, <-- row of sum cell
            "col": <col>, <-- column of sum cell
            "sums": [[<direction>, <sum>, <num_of_summands>],...]  <-- direction = "H"(orizontal) or "V"(ertical)
            },
            ...
        ]   
     }
     */
    public static function createFromJson($json)
    {
        $puzzle = new Puzzle();
        
        $data = json_decode($json);
        foreach ($data->groups as $group) {
            $row = intval($group->row);
            $col = intval($group->col);
            foreach ($group->sums as $sumData) {
                $direction = $sumData[0] === "H" ? self::HORIZONTAL : self::VERTICAL;
                $sum = intval($sumData[1]);
                $nSummands = intval($sumData[2]);
                $puzzle->addGroup($sum, $nSummands, $row, $col, $direction);    
            }
        }
        
        return $puzzle;
    }
    
    public function __construct()
    {        
        $this->cellFact = new CellFactory();
        $this->cells = [];
        $this->groupFact = new GroupFactory();
        $this->groups = [];
    }
    
    public function addGroup($sum, $nSummands, $row, $col, $direction)
    {
        $group = $this->groupFact->createGroup($sum);
        array_push($this->groups, $group);
        
        if ($direction == self::HORIZONTAL) {
            for ($i=1; $i<=$nSummands; $i++) {
                $group->addCell($this->getOrCreateCell($row, $col + $i, true));
            }
        } else {
            for ($i=1; $i<=$nSummands; $i++) {
                $group->addCell($this->getOrCreateCell($row + $i, $col, true));
            }
        }
    }
    
    public function solve($verbose=false)
    {
        $this->initCellValues();
        
        $stack = $this->groups;
        while (!empty($stack)) {
            $group = array_pop($stack);
            $toAdjust = $group->adjust();
            $stack = array_merge($this->groupsNotInStack($toAdjust, $stack), $stack);
            if ($verbose) {
                echo count($stack) . "<br>";
            }
        }
    }
    
    public function getHeight()
    {
        $rows = array_map(function($cell) {return $cell->getRow();}, $this->getCells());
        return max($rows) + 1;
    }
    
    public function getWidth()
    {
        $cols = array_map(function($cell) {return $cell->getColumn();}, $this->getCells());
        return max($cols) + 1;
    }
    
    public function getCells()
    {
        $cells = [];
        foreach (array_values($this->cells) as $rowCells) {
            foreach(array_values($rowCells) as $cell) {
                array_push($cells, $cell);
            }
        }
        return $cells;
    }
    
    public function getCell($row, $col)
    {
        return $this->getOrCreateCell($row, $col);
    }
    
    private $cellFact;
    private $cells;
    private $groupFact;
    private $groups;

    private function getOrCreateCell($row, $col, $createIfNotExisting=false)
    {
        if (array_key_exists($row, $this->cells)) {
            $rowCells = &$this->cells[$row];
            if (array_key_exists($col, $rowCells)) {
                return $rowCells[$col];
            } else {
                if ($createIfNotExisting) {
                    $cell = $this->cellFact->createCell($row, $col);
                    $rowCells[$col] = $cell;
                    return $cell;
                } else {
                    return null;
                }
            }
        } else {
            if ($createIfNotExisting) {
                $cell = $this->cellFact->createCell($row, $col);
                $this->cells[$row] = [$col => $cell];
                return $cell;
            } else {
                return null;    
            }
        }
    }
    
    private function groupsNotInStack($toAdjust, $stack)
    {
        $newGroups = [];
        foreach ($toAdjust as $grp) {
            $inStack = false;
            $gid = $grp->getId();
            foreach ($stack as $sgrp) {
                if ($gid === $sgrp->getId()) {
                    $inStack = true;
                    break;
                }
            }
            if (!$inStack) {
                array_push($newGroups, $grp);
            }
        }
        
        return $newGroups;
    }
    
    private function initCellValues()
    {
        foreach (array_values($this->cells) as $rowCells) {
            foreach(array_values($rowCells) as $cell) {
                $cell->values = $this->getPossibleValues($cell->getGroups());
            }
        }
    }
    
    private function getPossibleValues($groups)
    {
        $values = [];
        $set = null;
        
        foreach ($groups as $group) {
            $gset = Group::getSums()->getSummandsSet($group->getSum(), $group->getSize());
            if ($set === null) {
                $set = $gset;
            } else {
                $set = $set->intersection($gset);
            }
        }
        
        return $set->toArray();
    }

}